import os, json, joblib, numpy as np, pandas as pd
# 导入常用库：os用于操作系统交互，json用于处理JSON数据，joblib用于模型持久化，numpy和pandas用于数据处理
from pathlib import Path
import warnings 
warnings.filterwarnings("ignore")
# 忽略警告信息，保持输出整洁


from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.utils.class_weight import compute_class_weight

from tensorflow.keras.utils import Sequence, to_categorical, pad_sequences
from tensorflow.keras.models import Model, load_model
from tensorflow.keras.layers import (
    Input, Conv1D, BatchNormalization, Activation, add, MaxPooling1D, Dropout,
    Bidirectional, LSTM, GlobalAveragePooling1D, Dense, Multiply, Reshape,
    Lambda, Concatenate, GRU, GaussianNoise
)
from tensorflow.keras.regularizers import l2
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras import backend as K
import tensorflow as tf
import polars as pl
from sklearn.model_selection import StratifiedGroupKFold
from scipy.spatial.transform import Rotation as R
import random

def seed_everything(seed):
    """
    设置所有相关库的随机种子，保证实验的可复现性。
    参数：
        seed (int): 随机种子数值
    """
    os.environ['PYTHONHASHSEED'] = str(seed)
    random.seed(seed)
    np.random.seed(seed)
    tf.random.set_seed(seed)
    tf.experimental.numpy.random.seed(seed)
    os.environ['TF_CUDNN_DETERMINISTIC'] = '1'
    os.environ['TF_DETERMINISTIC_OPS'] = '1'
seed_everything(seed=42)
# (Competition metric will only be imported when TRAINing)
TRAIN = True                     # ← set to True when you want to train
RAW_DIR = Path(".")  # 本地运行时使用当前目录
PRETRAINED_DIR = Path("/kaggle/input/pretrained-model")  # used when TRAIN=False
EXPORT_DIR = Path("./")                                    # artefacts will be saved here
BATCH_SIZE = 64
PAD_PERCENTILE = 95
LR_INIT = 5e-4
WD = 3e-3
MIXUP_ALPHA = 0.4
EPOCHS = 160
PATIENCE = 40


print("▶ 导入完成 · tensorflow 版本:", tf.__version__)
#Tensor Manipulations
# 以下为张量操作相关的辅助函数

def time_sum(x):
    """对输入张量x在时间维(axis=1)上求和"""
    return K.sum(x, axis=1)

def squeeze_last_axis(x):
    """去除张量最后一维（常用于注意力权重处理）"""
    return tf.squeeze(x, axis=-1)

def expand_last_axis(x):
    """在张量最后一维扩展一维（常用于注意力权重处理）"""
    return tf.expand_dims(x, axis=-1)

def se_block(x, reduction=8):
    """
    Squeeze-and-Excitation（SE）注意力模块。
    参数：
        x: 输入特征张量
        reduction: 通道压缩比
    返回：
        加权后的特征张量
    """
    ch = x.shape[-1]
    se = GlobalAveragePooling1D()(x)
    se = Dense(ch // reduction, activation='relu')(se)
    se = Dense(ch, activation='sigmoid')(se)
    se = Reshape((1, ch))(se)
    return Multiply()([x, se])

# Residual CNN Block with SE
# 带有SE注意力机制的残差卷积块

def residual_se_cnn_block(x, filters, kernel_size, pool_size=2, drop=0.3, wd=1e-4):
    """
    构建带有SE注意力和残差连接的卷积块。
    参数：
        x: 输入张量
        filters: 卷积核数量
        kernel_size: 卷积核大小
        pool_size: 池化窗口大小
        drop: dropout比例
        wd: L2正则化系数
    返回：
        经过卷积、SE、残差、池化和dropout后的张量
    """
    shortcut = x
    for _ in range(2):
        x = Conv1D(filters, kernel_size, padding='same', use_bias=False,
                   kernel_regularizer=l2(wd))(x)
        x = BatchNormalization()(x)
        x = Activation('relu')(x)
    x = se_block(x)
    if shortcut.shape[-1] != filters:
        shortcut = Conv1D(filters, 1, padding='same', use_bias=False,
                          kernel_regularizer=l2(wd))(shortcut)
        shortcut = BatchNormalization()(shortcut)
    x = add([x, shortcut])
    x = Activation('relu')(x)
    x = MaxPooling1D(pool_size)(x)
    x = Dropout(drop)(x)
    return x

def attention_layer(inputs):
    """
    简单的注意力机制层，对输入序列加权求和。
    参数：
        inputs: 输入张量
    返回：
        加权后的上下文向量
    """
    score = Dense(1, activation='tanh')(inputs)
    score = Lambda(squeeze_last_axis)(score)
    weights = Activation('softmax')(score)
    weights = Lambda(expand_last_axis)(weights)
    context = Multiply()([inputs, weights])
    context = Lambda(time_sum)(context)
    return context
# Normalizes and cleans the time series sequence. 

# 归一化并清洗时间序列数据

def preprocess_sequence(df_seq: pd.DataFrame, feature_cols: list[str], scaler: StandardScaler):
    """
    对单个序列数据进行特征填充、归一化处理。
    参数：
        df_seq: 单个序列的DataFrame
        feature_cols: 需要处理的特征列名
        scaler: 训练阶段拟合的StandardScaler
    返回：
        归一化后的特征矩阵
    """
    mat = df_seq[feature_cols].ffill().bfill().fillna(0).values
    return scaler.transform(mat).astype('float32')

# MixUp the data argumentation in order to regularize the neural network. 
# Mixup数据增强生成器，用于正则化神经网络
class MixupGenerator(Sequence):
    """
    Mixup数据增强生成器，继承自Keras的Sequence。
    在每个batch中对样本进行Mixup操作。
    """
    def __init__(self, X, y, batch_size, alpha=0.2):
        self.X, self.y = X, y
        self.batch = batch_size
        self.alpha = alpha
        self.indices = np.arange(len(X))
    def __len__(self):
        return int(np.ceil(len(self.X) / self.batch))
    def __getitem__(self, i):
        idx = self.indices[i*self.batch:(i+1)*self.batch]
        Xb, yb = self.X[idx], self.y[idx]
        lam = np.random.beta(self.alpha, self.alpha)
        perm = np.random.permutation(len(Xb))
        X_mix = lam * Xb + (1-lam) * Xb[perm]
        y_mix = lam * yb + (1-lam) * yb[perm]
        return X_mix, y_mix
    def on_epoch_end(self):
        np.random.shuffle(self.indices)

def remove_gravity_from_acc(acc_data, rot_data):
    """
    利用四元数旋转信息从加速度数据中去除重力分量，得到线性加速度。
    参数：
        acc_data: 加速度数据（DataFrame或ndarray）
        rot_data: 旋转四元数数据（DataFrame或ndarray）
    返回：
        线性加速度数组
    """
    if isinstance(acc_data, pd.DataFrame):
        acc_values = acc_data[['acc_x', 'acc_y', 'acc_z']].values
    else:
        acc_values = acc_data

    if isinstance(rot_data, pd.DataFrame):
        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values
    else:
        quat_values = rot_data

    num_samples = acc_values.shape[0]
    linear_accel = np.zeros_like(acc_values)
    
    gravity_world = np.array([0, 0, 9.81])

    for i in range(num_samples):
        # 检查四元数是否有效
        if np.all(np.isnan(quat_values[i])) or np.all(np.isclose(quat_values[i], 0)):
            linear_accel[i, :] = acc_values[i, :] 
            continue

        try:
            rotation = R.from_quat(quat_values[i])
            gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)
            linear_accel[i, :] = acc_values[i, :] - gravity_sensor_frame
        except ValueError:
             linear_accel[i, :] = acc_values[i, :]
             
    return linear_accel
def build_two_branch_model(pad_len, imu_dim, tof_dim, n_classes, wd=1e-4):
    """
    构建双分支神经网络模型：IMU分支+TOF/热成像分支，融合后接RNN和注意力机制。
    参数：
        pad_len: 输入序列长度
        imu_dim: IMU特征维度
        tof_dim: TOF+热成像特征维度
        n_classes: 分类类别数
        wd: L2正则化系数
    返回：
        Keras模型对象
    """
    inp = Input(shape=(pad_len, imu_dim+tof_dim))
    imu = Lambda(lambda t: t[:, :, :imu_dim])(inp)
    tof = Lambda(lambda t: t[:, :, imu_dim:])(inp)

    # IMU deep branch
    x1 = residual_se_cnn_block(imu, 64, 3, drop=0.1, wd=wd)
    x1 = residual_se_cnn_block(x1, 128, 5, drop=0.1, wd=wd)

    # TOF/Thermal lighter branch
    x2 = Conv1D(64, 3, padding='same', use_bias=False, kernel_regularizer=l2(wd))(tof)
    x2 = BatchNormalization()(x2); x2 = Activation('relu')(x2)
    x2 = MaxPooling1D(2)(x2); x2 = Dropout(0.2)(x2)
    x2 = Conv1D(128, 3, padding='same', use_bias=False, kernel_regularizer=l2(wd))(x2)
    x2 = BatchNormalization()(x2); x2 = Activation('relu')(x2)
    x2 = MaxPooling1D(2)(x2); x2 = Dropout(0.2)(x2)

    merged = Concatenate()([x1, x2])

    xa = Bidirectional(LSTM(128, return_sequences=True, kernel_regularizer=l2(wd)))(merged)
    xb = Bidirectional(GRU(128, return_sequences=True, kernel_regularizer=l2(wd)))(merged)
    xc = GaussianNoise(0.09)(merged)
    xc = Dense(16, activation='elu')(xc)
    
    x = Concatenate()([xa, xb, xc])
    x = Dropout(0.4)(x)
    x = attention_layer(x)

    for units, drop in [(256, 0.5), (128, 0.3)]:
        x = Dense(units, use_bias=False, kernel_regularizer=l2(wd))(x)
        x = BatchNormalization()(x); x = Activation('relu')(x)
        x = Dropout(drop)(x)

    out = Dense(n_classes, activation='softmax', kernel_regularizer=l2(wd))(x)
    return Model(inp, out)

tmp_model = build_two_branch_model(127,7,325,18)
if TRAIN:
    print("▶ 训练模式 – 正在加载数据集 …")
    df = pd.read_csv(RAW_DIR / "train.csv")

    train_dem_df = pd.read_csv(RAW_DIR / "train_demographics.csv")
    df_for_groups = pd.merge(df.copy(), train_dem_df, on='subject', how='left')

    le = LabelEncoder()
    df['gesture_int'] = le.fit_transform(df['gesture'])
    np.save(EXPORT_DIR / "gesture_classes.npy", le.classes_)
    gesture_classes = le.classes_

    print("  计算IMU基础特征（模、角度）…")
    # 计算IMU基础特征：加速度模、旋转角度
    df['acc_mag'] = np.sqrt(df['acc_x']**2 + df['acc_y']**2 + df['acc_z']**2)
    df['rot_angle'] = 2 * np.arccos(df['rot_w'].clip(-1, 1))
    
    print("  计算IMU导数特征（加速度变化率、角速度）…")
    # 计算IMU导数特征：加速度变化率、角速度
    df['acc_mag_jerk'] = df.groupby('sequence_id')['acc_mag'].diff().fillna(0)
    df['rot_angle_vel'] = df.groupby('sequence_id')['rot_angle'].diff().fillna(0)

    print("  去除重力分量并计算线性加速度特征…")
    # 去除重力分量，计算线性加速度及其模和导数
    linear_accel_list = []
    for _, group in df.groupby('sequence_id'):
        acc_data_group = group[['acc_x', 'acc_y', 'acc_z']]
        rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]
        linear_accel_group = remove_gravity_from_acc(acc_data_group, rot_data_group)
        linear_accel_list.append(pd.DataFrame(linear_accel_group, columns=['linear_acc_x', 'linear_acc_y', 'linear_acc_z'], index=group.index))
    
    df_linear_accel = pd.concat(linear_accel_list)
    df_linear_accel = df_linear_accel.sort_index()
    df[['linear_acc_x', 'linear_acc_y', 'linear_acc_z']] = df_linear_accel[['linear_acc_x', 'linear_acc_y', 'linear_acc_z']]

    df['linear_acc_mag'] = np.sqrt(df['linear_acc_x']**2 + df['linear_acc_y']**2 + df['linear_acc_z']**2)
    df['linear_acc_mag_jerk'] = df.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)

    # meta_cols、imu_cols、tof/thm特征等的构建
    meta_cols = { ... }

    imu_cols_base = ['linear_acc_x', 'linear_acc_y', 'linear_acc_z']
    imu_cols_base.extend([c for c in df.columns if c.startswith('rot_') and c not in ['rot_angle', 'rot_angle_vel']])
    
    imu_engineered_features = [
        'acc_mag', 'rot_angle',
        'acc_mag_jerk', 'rot_angle_vel',
        'linear_acc_mag', 'linear_acc_mag_jerk'
    ]
    imu_cols = imu_cols_base + imu_engineered_features
    imu_cols = list(dict.fromkeys(imu_cols))

    thm_cols_original = [c for c in df.columns if c.startswith('thm_')]
    
    tof_aggregated_cols_template = []
    for i in range(1, 6):
        tof_aggregated_cols_template.extend([f'tof_{i}_mean', f'tof_{i}_std', f'tof_{i}_min', f'tof_{i}_max'])

    final_feature_cols = imu_cols + thm_cols_original + tof_aggregated_cols_template
    imu_dim_final = len(imu_cols)
    tof_thm_aggregated_dim_final = len(thm_cols_original) + len(tof_aggregated_cols_template)
    
    print(f"  IMU（含工程特征与导数）{imu_dim_final} | 热成像+TOF聚合特征 {tof_thm_aggregated_dim_final} | 总特征数 {len(final_feature_cols)}")
    np.save(EXPORT_DIR / "feature_cols.npy", np.array(final_feature_cols))

    print("  构建带聚合TOF的序列并准备归一化…")
    # 构建序列特征矩阵，收集所有样本用于归一化
    seq_gp = df.groupby('sequence_id') 
    
    all_steps_for_scaler_list = []
    X_list_unscaled, y_list_int_for_stratify, lens = [], [], [] 

    for seq_id, seq_df_orig in seq_gp:
        seq_df = seq_df_orig.copy()

        for i in range(1, 6):
            pixel_cols_tof = [f"tof_{i}_v{p}" for p in range(64)]
            tof_sensor_data = seq_df[pixel_cols_tof].replace(-1, np.nan)
            seq_df[f'tof_{i}_mean'] = tof_sensor_data.mean(axis=1)
            seq_df[f'tof_{i}_std']  = tof_sensor_data.std(axis=1)
            seq_df[f'tof_{i}_min']  = tof_sensor_data.min(axis=1)
            seq_df[f'tof_{i}_max']  = tof_sensor_data.max(axis=1)
        
        mat_unscaled = seq_df[final_feature_cols].ffill().bfill().fillna(0).values.astype('float32')
        
        all_steps_for_scaler_list.append(mat_unscaled)
        X_list_unscaled.append(mat_unscaled)
        y_list_int_for_stratify.append(seq_df['gesture_int'].iloc[0])
        lens.append(len(mat_unscaled))

    print("  拟合StandardScaler归一化器…")
    # 拟合归一化器
    all_steps_concatenated = np.concatenate(all_steps_for_scaler_list, axis=0)
    scaler = StandardScaler().fit(all_steps_concatenated)
    joblib.dump(scaler, EXPORT_DIR / "scaler.pkl")
    del all_steps_for_scaler_list, all_steps_concatenated

    print("  对序列进行归一化和填充…")
    # 对每个序列归一化并填充到统一长度
    X_scaled_list = [scaler.transform(x_seq) for x_seq in X_list_unscaled]
    del X_list_unscaled

    pad_len = int(np.percentile(lens, PAD_PERCENTILE))
    np.save(EXPORT_DIR / "sequence_maxlen.npy", pad_len)
    
    X = pad_sequences(X_scaled_list, maxlen=pad_len, padding='post', truncating='post', dtype='float32')
    del X_scaled_list
    
    y_int_for_stratify = np.array(y_list_int_for_stratify)
    y = to_categorical(y_int_for_stratify, num_classes=len(le.classes_))

    print("  划分数据集并准备训练…")
    # 划分训练集和验证集
    X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, random_state=82, stratify=y_int_for_stratify)

    cw_vals = compute_class_weight('balanced', classes=np.arange(len(le.classes_)), y=y_int_for_stratify)
    class_weight = dict(enumerate(cw_vals))

    model = build_two_branch_model(pad_len, imu_dim_final, tof_thm_aggregated_dim_final, len(le.classes_), wd=WD)
    
    steps = len(X_tr) // BATCH_SIZE
    lr_sched = tf.keras.optimizers.schedules.CosineDecayRestarts(5e-4, first_decay_steps=15 * steps) 
    
    model.compile(optimizer=Adam(lr_sched),
                  loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1),
                  metrics=['accuracy'])

    train_gen = MixupGenerator(X_tr, y_tr, batch_size=BATCH_SIZE, alpha=MIXUP_ALPHA)
    cb = EarlyStopping(patience=PATIENCE, restore_best_weights=True, verbose=1, monitor='val_accuracy', mode='max')
    
    print("  开始模型训练…")
    # 启动模型训练
    model.fit(train_gen, epochs=EPOCHS, validation_data=(X_val, y_val),
              class_weight=class_weight, callbacks=[cb], verbose=1)

    model.save(EXPORT_DIR / "gesture_two_branch_mixup.h5")
    print("✔ 训练完成 – 相关文件已保存到", EXPORT_DIR)

    from cmi_2025_metric_copy_for_import import CompetitionMetric
    preds_val = model.predict(X_val).argmax(1)
    true_val_int  = y_val.argmax(1)
    
    h_f1 = CompetitionMetric().calculate_hierarchical_f1(
        pd.DataFrame({'gesture': le.classes_[true_val_int]}),
        pd.DataFrame({'gesture': le.classes_[preds_val]}))
    print("验证集 H‑F1 =", round(h_f1, 4))

else:
    print("▶ 推理模式 – 正在从", PRETRAINED_DIR, "加载模型与相关文件…")
    final_feature_cols = np.load(PRETRAINED_DIR / "feature_cols.npy", allow_pickle=True).tolist()
    pad_len        = int(np.load(PRETRAINED_DIR / "sequence_maxlen.npy"))
    scaler         = joblib.load(PRETRAINED_DIR / "scaler.pkl")
    gesture_classes = np.load(PRETRAINED_DIR / "gesture_classes.npy", allow_pickle=True)

    temp_imu_cols = [c for c in final_feature_cols if c.startswith('acc_') or c.startswith('rot_')]
    imu_dim_final = len(temp_imu_cols)
    tof_thm_aggregated_dim_final = len(final_feature_cols) - imu_dim_final

    custom_objs = {
        'time_sum': time_sum,
        'squeeze_last_axis': squeeze_last_axis,
        'expand_last_axis': expand_last_axis,
        'se_block': se_block,
        'residual_se_cnn_block': residual_se_cnn_block,
        'attention_layer': attention_layer,
    }
    model = load_model(PRETRAINED_DIR / "gesture_two_branch_mixup.h5",
                       compile=False, custom_objects=custom_objs)
    print("  模型、归一化器、特征列、pad长度加载完成 – 可以进行评估")
def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:
    """
    单条序列推理函数，供评测服务器调用。
    参数：
        sequence: polars的DataFrame，单条序列的原始数据
        demographics: polars的DataFrame，人口统计信息（可选）
    返回：
        预测的手势类别（字符串）
    """
    df_seq = sequence.to_pandas()

    # 特征工程，与训练阶段保持一致
    df_seq['acc_mag'] = np.sqrt(df_seq['acc_x']**2 + df_seq['acc_y']**2 + df_seq['acc_z']**2)
    df_seq['rot_angle'] = 2 * np.arccos(df_seq['rot_w'].clip(-1, 1))
    df_seq['acc_mag_jerk'] = df_seq['acc_mag'].diff().fillna(0)
    df_seq['rot_angle_vel'] = df_seq['rot_angle'].diff().fillna(0)

    acc_cols_for_gravity_removal = ['acc_x', 'acc_y', 'acc_z']
    rot_cols_for_gravity_removal = ['rot_x', 'rot_y', 'rot_z', 'rot_w']

    if not all(col in df_seq.columns for col in acc_cols_for_gravity_removal + rot_cols_for_gravity_removal):
        print(f"警告：推理时缺少原始加速度/旋转列，重力去除失败，将直接使用原始加速度。")
        df_seq['linear_acc_x'] = df_seq.get('acc_x', 0)
        df_seq['linear_acc_y'] = df_seq.get('acc_y', 0)
        df_seq['linear_acc_z'] = df_seq.get('acc_z', 0)
    else:
        acc_data_seq = df_seq[acc_cols_for_gravity_removal]
        rot_data_seq = df_seq[rot_cols_for_gravity_removal]
        linear_accel_seq_arr = remove_gravity_from_acc(acc_data_seq, rot_data_seq)
        
        df_seq['linear_acc_x'] = linear_accel_seq_arr[:, 0]
        df_seq['linear_acc_y'] = linear_accel_seq_arr[:, 1]
        df_seq['linear_acc_z'] = linear_accel_seq_arr[:, 2]
    
    df_seq['linear_acc_mag'] = np.sqrt(df_seq['linear_acc_x']**2 + df_seq['linear_acc_y']**2 + df_seq['linear_acc_z']**2)
    df_seq['linear_acc_mag_jerk'] = df_seq['linear_acc_mag'].diff().fillna(0)
    
    for i in range(1, 6): 
        pixel_cols_tof = [f"tof_{i}_v{p}" for p in range(64)]
        if not all(col in df_seq.columns for col in pixel_cols_tof):
            print(f"警告：推理时tof_{i}的像素列缺失，聚合特征将填充为0。")
            df_seq[f'tof_{i}_mean'] = 0
            df_seq[f'tof_{i}_std']  = 0
            df_seq[f'tof_{i}_min']  = 0
            df_seq[f'tof_{i}_max']  = 0
            continue

        tof_sensor_data = df_seq[pixel_cols_tof].replace(-1, np.nan)
        df_seq[f'tof_{i}_mean'] = tof_sensor_data.mean(axis=1)
        df_seq[f'tof_{i}_std']  = tof_sensor_data.std(axis=1)
        df_seq[f'tof_{i}_min']  = tof_sensor_data.min(axis=1)
        df_seq[f'tof_{i}_max']  = tof_sensor_data.max(axis=1)
        
    if 'tof_range_across_sensors' in final_feature_cols:
        tof_mean_cols_for_contrast = [f'tof_{i}_mean' for i in range(1, 6) if f'tof_{i}_mean' in df_seq.columns]
        thm_cols_for_contrast = [f'thm_{i}' for i in range(1, 6) if f'thm_{i}' in df_seq.columns]

        if tof_mean_cols_for_contrast:
            tof_values_for_contrast = df_seq[tof_mean_cols_for_contrast]
            df_seq['tof_range_across_sensors'] = tof_values_for_contrast.max(axis=1) - tof_values_for_contrast.min(axis=1)
            df_seq['tof_std_across_sensors'] = tof_values_for_contrast.std(axis=1)
        else:
            df_seq['tof_range_across_sensors'] = 0
            df_seq['tof_std_across_sensors'] = 0

        if thm_cols_for_contrast:
            thm_values_for_contrast = df_seq[thm_cols_for_contrast]
            df_seq['thm_range_across_sensors'] = thm_values_for_contrast.max(axis=1) - thm_values_for_contrast.min(axis=1)
            df_seq['thm_std_across_sensors'] = thm_values_for_contrast.std(axis=1)
        else:
            df_seq['thm_range_across_sensors'] = 0
            df_seq['thm_std_across_sensors'] = 0
        
    df_seq_final_features = pd.DataFrame(index=df_seq.index)
    for col_name in final_feature_cols:
        if col_name in df_seq.columns:
            df_seq_final_features[col_name] = df_seq[col_name]
        else:
            print(f"严重错误：模型期望的特征 '{col_name}' 未在推理数据中生成，已填充为0。请检查特征工程流程！")
            df_seq_final_features[col_name] = 0 
            
    mat_unscaled = df_seq_final_features.ffill().bfill().fillna(0).values.astype('float32')
    
    mat_scaled = scaler.transform(mat_unscaled)
    
    pad_input = pad_sequences([mat_scaled], maxlen=pad_len, padding='post', truncating='post', dtype='float32')
    
    idx = int(model.predict(pad_input, verbose=0).argmax(1)[0])
    return str(gesture_classes[idx])

import kaggle_evaluation.cmi_inference_server
# 启动推理服务器，注册predict函数
inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)

if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):
    inference_server.serve()
else:
    inference_server.run_local_gateway(
        data_paths=(
            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',
            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',
        )
    )